package com.whub507.msgmanager.sso.client.sso;


import com.fasterxml.jackson.databind.ObjectMapper;
import com.whub507.msgmanager.sso.client.auth.IdentityToken;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.jwt.Jwt;
import org.springframework.security.jwt.JwtHelper;
import org.springframework.security.jwt.crypto.sign.SignerVerifier;
import org.springframework.stereotype.Component;
import org.springframework.web.client.HttpStatusCodeException;
import org.springframework.web.client.RestTemplate;

import javax.annotation.PostConstruct;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

/**
 * SSO Client验签服务
 * 
 * @author wcl
 *
 */
@Component
public class SSOService {

	@Value("${drap_platform.sso_server.rootpath}/cert/mine")
	private String download_cert_url;

	@Value("${drap_platform.sso_server.rootpath}/auth/getIssuer")
	private String get_issuer_url;

	@Value("${drap_platform.sso_server.rootpath}/auth/transformCrossToken")
	private String transform_cross_token_url;

	@Value("${drap_platform.sso_server.rootpath}/auth/getTokenByCode")
	private String getTokenByCode_url;

	@Autowired
	private ObjectMapper mapper;

	private RestTemplate restTemplate;

	// 本级证书
	private byte[] certBytes;

	// 本级令牌发行方
	private String issuer;

	private ThreadLocal<SignerVerifier> tlverifier = new ThreadLocal<>();

	private static Logger logger = LoggerFactory.getLogger(SSOService.class);

	/**
	 * 使用一次性认证码换取用户身份令牌，并验签
	 * 
	 * @param sso_code
	 * @param app_id
	 *            应用唯一标识,对应统一权限分系统中应用ID；用于记录令牌访问的应用集；可以为空
	 * @return
	 */
	public Object[] getIdentityTokenByCode(String sso_code, String app_id) {
		try {
			logger.info(String.format("SSOService getIdentityTokenByCode begin, sso_code:[%s], app_id:[%s], url:[%s]", sso_code,
					app_id, getTokenByCode_url));
			String sso_token = getTokenByCode(sso_code);
			logger.info("SSOService getIdentityTokenByCode success , sso_code:[" + sso_code + "] , app_id:[" + app_id
					+ "]");
			return verify(sso_token, app_id);
		} catch (Exception e) {
			if (e instanceof HttpStatusCodeException) {
				HttpStatusCodeException he = (HttpStatusCodeException) e;
				HttpStatus status = he.getStatusCode();
				String statusText = he.getStatusText();
				HttpHeaders headers = he.getResponseHeaders();
				String bodyString = he.getResponseBodyAsString();
				logger.error(String.format(
						"SSOService getIdentityTokenByCode failed, sso_code:[%s], app_id:[%s], url:[%s], statusCode:[%s], statusText:[%s], response headers:[%s], response body:[%s]",
						sso_code, app_id, getTokenByCode_url, (null != status) ? status.toString() : "NULL Status",
						statusText, (null != headers) ? headers.toString() : "NULL HEADERS", bodyString), e);
			} else {
				logger.error(String.format(
						"SSOService getIdentityTokenByCode failed , sso_code:[%s] , app_id:[%s], url:[%s]", sso_code,
						app_id, getTokenByCode_url), e);
			}
			throw new RuntimeException(e);
		}
	}

	/**
	 * 验签：验证令牌合法性,含跨级转换，适用于应用入口集成SSO
	 * 
	 * @param token
	 * @param app_id
	 *            应用唯一标识,对应统一权限分系统中应用ID；用于记录令牌访问的应用集；可以为空
	 * @return [JWT,IdentityToken]
	 */
	public Object[] verify(String token, String app_id) {
		try {
			Jwt dt = JwtHelper.decode(token);
			IdentityToken idToken = mapper.readValue(dt.getClaims(), IdentityToken.class);

			Long newDate = System.currentTimeMillis();
			Long exp = idToken.getExp();
			if (exp != null && newDate > exp) {
				logger.error("SSOService verify failed , the Token has expired , token:[" + token + "] , app_id:["
						+ app_id + "] , exp[" + new Date(exp) + "]");
				throw new RuntimeException(String.format("TokenExpiredException:The Token has expired on %s.", exp));
			}
			// 是本级发放的token
			if (idToken.getIss().equals(getIssuer())) {
				// 尝试本级验签
				dt.verifySignature(getVerifier());
			} else {
				// 尝试转换跨级令牌
				token = transformCrossToken(token, app_id);
				dt = JwtHelper.decode(token);
				idToken = mapper.readValue(dt.getClaims(), IdentityToken.class);
			}
			logger.warn("SSOService verify success.");
			return new Object[] { token, idToken };
		} catch (Exception e) {
			clearVerifier();
			// if (!e.getMessage().startsWith("TokenExpiredException:")) {
			logger.error("SSOService verify failed , token:[" + token + "] , app_id:[" + app_id + "]", e);
			// }
			throw new RuntimeException(e);
		}
	}

	private SignerVerifier getVerifier() throws Exception {
		SignerVerifier verifier = tlverifier.get();
		if (null != verifier) {
			return verifier;
		} else {
			if (this.certBytes == null) {
				this.certBytes = downloadCert();
			}
			verifier = new SSOVerifySigner(this.certBytes);
			tlverifier.set(verifier);
			return verifier;
		}
	}

	private String getIssuer() {
		if (this.issuer == null) {
			ResponseEntity<String> response = restTemplate.getForEntity(get_issuer_url, String.class);
			this.issuer = response.getBody();

			logger.info(" Get issuer success from sso server. The value length is ["
					+ ((null == this.issuer) ? 0 : this.issuer.length()) + "]");
		}
		return this.issuer;
	}

	private void clearVerifier() {
		tlverifier.remove();
	}

	private byte[] downloadCert() {
		ResponseEntity<byte[]> response = restTemplate.getForEntity(download_cert_url, byte[].class);
		logger.warn(" DownloadCert success from sso server.");
		return response.getBody();
	}

	private String transformCrossToken(String token, String app_id) {
		String url = transform_cross_token_url + "?sso_token=" + token + "&app_id=" + app_id;
		ResponseEntity<String> response = restTemplate.getForEntity(url, String.class);
		return response.getBody();
	}

	private String getTokenByCode(String code) {
		HttpHeaders httpHeaders = new HttpHeaders();
		httpHeaders.add("Content-Type", "application/json; charset=UTF-8");
		Map<String, String> data = new HashMap<String, String>();
		data.put("sso_code", code);
		HttpEntity<Map<String, String>> entity = new HttpEntity<Map<String, String>>(data, httpHeaders);
		ResponseEntity<String> response = restTemplate.postForEntity(getTokenByCode_url, entity, String.class);

		return response.getBody();
	}

	@PostConstruct
	public void init() {
		SimpleClientHttpRequestFactory4Https requestFactory = new SimpleClientHttpRequestFactory4Https();

		requestFactory.setReadTimeout(120 * 1000);

		this.restTemplate = new RestTemplate(requestFactory);
	}

	/**
	 * 使用一次性认证码换取用户身份令牌，但不进行验签
	 * 
	 * @param sso_code
	 * @param app_id
	 *            应用唯一标识,对应统一权限分系统中应用ID；用于记录令牌访问的应用集；可以为空
	 * @return
	 */
	public Object[] getIdentityTokenByCodeNegVerify(String sso_code, String app_id) {
		try {
			logger.info(String.format("SSOService getIdentityTokenByCodeNegVerify begin, sso_code:[%s], app_id:[%s], url:[%s]", sso_code,
					app_id, getTokenByCode_url));
			String sso_token = getTokenByCode(sso_code);
			logger.warn("SSOService getIdentityTokenByCodeNegVerify success.");
			Jwt dt = JwtHelper.decode(sso_token);
			IdentityToken idToken = mapper.readValue(dt.getClaims(), IdentityToken.class);

			Long newDate = System.currentTimeMillis();
			Long exp = idToken.getExp();
			if (exp != null && newDate > exp) {
				throw new RuntimeException(String.format("TokenExpiredException:The Token has expired on %s.", exp));
			}
			return new Object[] { sso_token, idToken };
		} catch (Exception e) {
			if (e instanceof HttpStatusCodeException) {
				HttpStatusCodeException he = (HttpStatusCodeException) e;
				HttpStatus status = he.getStatusCode();
				String statusText = he.getStatusText();
				HttpHeaders headers = he.getResponseHeaders();
				String bodyString = he.getResponseBodyAsString();
				logger.error(String.format(
						"SSOService getIdentityTokenByCodeWithoutVerify failed, sso_code:[%s], app_id:[%s], url:[%s], statusCode:[%s], statusText:[%s], response headers:[%s], response body:[%s]",
						sso_code, app_id, getTokenByCode_url, (null != status) ? status.toString() : "NULL Status",
						statusText, (null != headers) ? headers.toString() : "NULL HEADERS", bodyString), e);
			} else {
				logger.error(String.format(
						"SSOService getIdentityTokenByCodeWithoutVerify failed , sso_code:[%s] , app_id:[%s], url:[%s]",
						sso_code, app_id, getTokenByCode_url), e);
			}
			throw new RuntimeException(e);
		}
	}

}
